package de.mmis.core.publishsubscribe.filter;

import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

import de.mmis.core.base.abstracttree.Tree;
import de.mmis.core.base.abstracttree.serializer.Serializable;
import de.mmis.core.base.event.Event;

/**
 * A <code>null</code> Filter matches any {@link Event}
 * 
 * @author Martin Nyolt
 * 
 */
public abstract class Filter implements Serializable {

	public static class FilterException extends Exception {

		private static final long serialVersionUID = -8594565835617372048L;

		public FilterException() {
			super();
		}

		public FilterException(String message) {
			super(message);
		}

		public FilterException(Throwable cause) {
			super(cause);
		}

		public FilterException(String message, Throwable cause) {
			super(message, cause);
		}
	}

	/**
	 * This empty Filter passes all Events.
	 */
	public static Filter empty() {
		return new AtomicFilter();
	}

	/**
	 * This filter checks if a given exists in the event data
	 */
	public static Filter keyExists(String key) {
		return new AtomicFilter(key);
	}

	/**
	 * This filter compares two values, both being a value in the event data,
	 * given by a key. If a key does not exists, the filter always fails.
	 * 
	 * @param op
	 *            The {@link FilterOperator} used to compare both values
	 */
	public static Filter compare(FilterOperator op, String key1, String key2) {
		return new AtomicFilter(op, new FilterValue(key1),
				new FilterValue(key2));
	}

	/**
	 * This filter compares two values, the first being a value in the event
	 * data, given by a key and the second a raw value. If the key does not
	 * exists, or the value is null, the filter always fails.
	 * 
	 * @param op
	 *            The {@link FilterOperator} used to compare both values
	 */
	public static Filter compare(FilterOperator op, String key1, Tree value2) {
		return new AtomicFilter(op, new FilterValue(key1), new FilterValue(
				value2));
	}

	/**
	 * This filter compares two values, the second being a value in the event
	 * data, given by a key and the first a raw value. If the key does not
	 * exists, or the value is null, the filter always fails.
	 * 
	 * @param op
	 *            The {@link FilterOperator} used to compare both values
	 */
	public static Filter compare(FilterOperator op, Tree value1, String key2) {
		return new AtomicFilter(op, new FilterValue(value1), new FilterValue(
				key2));
	}

	/**
	 * This filter compares two values, both being a raw value. If one of the
	 * values is null, the filter always fails.
	 * 
	 * @param op
	 *            The {@link FilterOperator} used to compare both values
	 */
	public static Filter compare(FilterOperator op, Tree value1, Tree value2) {
		return new AtomicFilter(op, new FilterValue(value1), new FilterValue(
				value2));
	}

	/**
	 * This filter compares two values. The first is a value in the event, the
	 * second a value of a specific device given by the id. If one of the values
	 * is null, the filter always fails.
	 * 
	 * @param op
	 *            The {@link FilterOperator} used to compare both values
	 */
	public static Filter compare1(FilterOperator op, String key1, String key2, // TODO
																				// MN:
																				// fix
																				// here
			String id2) {
		return new AtomicFilter(op, new FilterValue(key1), new FilterValue(id2,
				key2));
	}

	/**
	 * This filter compares two values. The first is a raw value, the second a
	 * value of a specific device given by the id. If one of the values is null,
	 * the filter always fails.
	 * 
	 * @param op
	 *            The {@link FilterOperator} used to compare both values
	 */
	public static Filter compare2(FilterOperator op, Tree value1, String key2,
			String id2) {
		return new AtomicFilter(op, new FilterValue(value1), new FilterValue(
				id2, key2));
	}

	/**
	 * This filter compares two values. The second is a value in the event, the
	 * first a value of a specific device given by the id. If one of the values
	 * is null, the filter always fails.
	 * 
	 * @param op
	 *            The {@link FilterOperator} used to compare both values
	 */
	public static Filter compare3(FilterOperator op, String key1, String id1,
			String key2) {
		return new AtomicFilter(op, new FilterValue(id1, key1),
				new FilterValue(key2));
	}

	/**
	 * This filter compares two values. The second is a raw value, the first a
	 * value of a specific device given by the id. If one of the values is null,
	 * the filter always fails.
	 * 
	 * @param op
	 *            The {@link FilterOperator} used to compare both values
	 */
	public static Filter compare4(FilterOperator op, String key1, String id1,
			Tree value2) {
		return new AtomicFilter(op, new FilterValue(id1, key1),
				new FilterValue(value2));
	}

	/**
	 * This filter compares two values. Both are a value of a specific device
	 * given by the id. If one of the values is null, the filter always fails.
	 * 
	 * @param op
	 *            The {@link FilterOperator} used to compare both values
	 */
	public static Filter compare5(FilterOperator op, String key1, String id1,
			String key2, String id2) {
		return new AtomicFilter(op, new FilterValue(id1, key1),
				new FilterValue(id2, key2));
	}

	/**
	 * This filter will match an event iff the given filter will not.
	 */
	public static Filter negate(Filter f) {
		return new CompoundFilter(f);
	}

	/**
	 * This filter matches iff the left-associative computation of all filters
	 * with the same {@link BooleanOperator} matches. If only filter is given,
	 * this class behaves like this filter.
	 * 
	 * @author Martin Nyolt
	 * 
	 */
	private static Filter multiFilter(BooleanOperator op, Filter filter1,
			Filter... filters) {
		if (filters == null || filters.length == 0) {
			return filter1;
		}

		Filter f;

		f = new CompoundFilter(op, filter1, filters[0]);
		for (int i = 1; i < filters.length; i++)
			f = new CompoundFilter(op, f, filters[i]);

		return f;
	}

	/**
	 * This filter will match an event iff all given filters will match. This
	 * conjunction computed left-associative.
	 */
	public static Filter conjunction(Filter filter1, Filter... filters) {
		return multiFilter(BooleanOperator.AND, filter1, filters);
	}

	/**
	 * This filter will match an event iff at least on of the given filters will
	 * match. This disjunction computed left-associative.
	 */
	public static Filter disjunction(Filter filter1, Filter... filters) {
		return multiFilter(BooleanOperator.OR, filter1, filters);
	}

	/**
	 * This filter will match an event iff the exclusive disjunction of the
	 * given filters will match, that is, if an odd number of the filters match.
	 * This exclusive disjunction is computed left-associative.
	 */
	public static Filter exDisjunction(Filter filter1, Filter... filters) {
		return multiFilter(BooleanOperator.XOR, filter1, filters);
	}

	/**
	 * This filter will match an event iff the left-associative implication of
	 * the given filters will match, that is, if the first filter matches, every
	 * filter must match.
	 */
	public static Filter implication(Filter filter1, Filter... filters) {
		return multiFilter(BooleanOperator.IMPLIES, filter1, filters);
	}

	/**
	 * This filter will match an event iff the left-associative equivalance of
	 * the given filters will match, that is, ((f1 <=> f2) <=> f3) <=> f4 ...<br />
	 * If two filters are given this matches iff either both match or none
	 * matches.
	 */
	public static Filter equivalence(Filter filter1, Filter... filters) {
		return multiFilter(BooleanOperator.IFF, filter1, filters);
	}

	public abstract boolean matches(Map<String, Tree> eventData,
			HashMap<String, Map<String, Tree>> worldstate)
			throws FilterException;

	/**
	 * Tests if two Filters are equals
	 */
	@Override
	public abstract boolean equals(Object obj);

	@Override
	public abstract int hashCode();

	public static Filter parse(String filter) throws IOException {
		Parser parser = new Parser(new Lexer(filter));
		if (!parser.parse())
			throw new IOException("Parsing of filter did not succeed: "
					+ filter);
		return parser.result;
	}
}
